/*
This file is part of [aconnect] library. 

Author: Artem Kustikov (kustikoff[at]tut.by)


This code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this code.

Permission is granted to anyone to use this code for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this code must not be misrepresented; you must
not claim that you wrote the original code. If you use this
code in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original code.

3. This notice may not be removed or altered from any source
distribution.
*/

#include "assert.h"
#include "compression/zlib_compressor.hpp"

#if defined (ACONNECT_USE_ZLIB)

namespace aconnect { namespace compression	{ namespace zlib
{

GzipCompressor::GzipCompressor (const GzipSettings& settings) throw(application_error)
	: _settings (settings), 
	_buffer (new char[_settings.chunkSize]),
	_firstCompress (true),
	_fileModificationTime (0),
	_crc (0L)
{
	using namespace legacy;

	// allocate deflate state
	_stream.zalloc = Z_NULL;
	_stream.zfree = Z_NULL;
	_stream.opaque = Z_NULL;
	

	int windowBits = abs(_settings.windowBits);
	if (windowBits <= 15 && windowBits >= 8)
		windowBits *= -1;

	int ret = legacy::deflateInit2 (&_stream, 
		_settings.level,
		_settings.method,
		windowBits, // produce clear 'deflate output
		_settings.memLevel,
		_settings.strategy);
	
	if (ret != Z_OK)
		throw application_error ("gzip/deflate compressor initialization failed, code: %d", ret);;


	// CRC32 initialization
	if (!_settings.deflateMode)
		_crc = legacy::crc32(_crc, Z_NULL, 0);
}

GzipCompressor::~GzipCompressor ()
{
	delete [] _buffer;
}


void GzipCompressor::complete() throw(application_error)
{
	// clean up and return
	deflateEnd (&_stream);
	
	//if (ret != Z_OK && ret != Z_DATA_ERROR)
	//	throw application_error ("gzip/deflate compressor finishing failed, code: %d", ret);

}

void GzipCompressor::compress(string_constptr content, const size_t contentSize, aconnect::string& storage, bool lastPart) throw(application_error)
{
	if (contentSize > 0)
		storage.reserve ( static_cast<size_t> (contentSize * _settings.ratio) );
	
	// write GZIP header
	if (contentSize > 0) {
		if (_firstCompress && !_settings.deflateMode)
			writeGzipHeader (storage);

		// reset header write flag
		_firstCompress = false;
	}

	if (_firstCompress && lastPart) // nothing was written
		return;
	
	_stream.next_in = reinterpret_cast<zlib::byte*>(const_cast<char*>(content));
	_stream.avail_in =  static_cast<zlib::uint>(contentSize);

	// run deflate() on input until output buffer not full, finish
	// compression if all of source has been read in
	do {
		_stream.avail_out = _settings.chunkSize;
		_stream.next_out = reinterpret_cast<zlib::byte*>(_buffer);

		int ret = deflate(&_stream, lastPart ? Z_FINISH : Z_SYNC_FLUSH);    // no bad return value 
		assert(ret != Z_STREAM_ERROR);  // state not clobbered 
		
		if (ret != Z_OK && ret != Z_STREAM_END)
			throw application_error ("gzip/deflate compressing failed, code: %d", ret);

		size_t processed = _settings.chunkSize - (size_t) _stream.avail_out;
		storage.append (_buffer, processed);

	} while (_stream.avail_out == 0);
	
	assert(_stream.avail_in == 0);     // all input will be used

	if (!_settings.deflateMode)
	{
		if (contentSize) {
			const zlib::byte* buf = reinterpret_cast<const zlib::byte*>(content);
			const zlib::uint length = static_cast<zlib::uint>(contentSize);

			_crc = legacy::crc32(_crc, buf, length);
		}
	
		if (lastPart && _stream.total_out > 0)
			writeGzipFooter (storage);
	}
}

size_t GzipCompressor::totalOut()
{
	return _stream.total_out;
}

// copied from Boost.Iostreams
void GzipCompressor::writeGzipHeader(aconnect::string& content) 
{
    // Calculate gzip header.
    bool has_name = !_fileName.empty();
    bool has_comment = !_comment.empty();

    std::string::size_type length =
        10 +
        (has_name ? _fileName.size() + 1 : 0) +
        (has_comment ? _comment.size() + 1 : 0);
    
    char flags =
        (has_name ? gzip::flags::name : 0) +
        (has_comment ? gzip::flags::comment : 0);

    char extra_flags =
		( _settings.level == best_compression ? gzip::extra_flags::best_compression : 0 ) +
        ( _settings.level == best_speed ? gzip::extra_flags::best_speed : 0 );

	if (content.capacity() < length) 
		content.reserve(length);
	
    content += gzip::magic::id1;                         // ID1.
    content += gzip::magic::id2;                         // ID2.
    content += gzip::method::deflate;                    // CM.
    content += static_cast<unsigned char>(flags);								// FLG.
    content += static_cast<unsigned char>(0xFF & _fileModificationTime);        // MTIME.
    content += static_cast<unsigned char>(0xFF & (_fileModificationTime >> 8));
    content += static_cast<unsigned char>(0xFF & (_fileModificationTime >> 16));
    content += static_cast<unsigned char>(0xFF & (_fileModificationTime >> 24));
    content += static_cast<unsigned char>(extra_flags);           // XFL.
	content += static_cast<unsigned char>(gzip::os::Unknown);     // OS.
    
	if (has_name) {
        content += _fileName;
        content += '\0';
    }
    if (has_comment) {
        content += _comment;
        content += '\0';
    }
}

void GzipCompressor::writeGzipFooter(aconnect::string& content)
{
	long totalIn = _stream.total_in;
	
	content += static_cast<unsigned char>(0xFF & _crc);        
    content += static_cast<unsigned char>(0xFF & (_crc >> 8));
    content += static_cast<unsigned char>(0xFF & (_crc >> 16));
    content += static_cast<unsigned char>(0xFF & (_crc >> 24));

	content += static_cast<unsigned char>(0xFF & totalIn);        
    content += static_cast<unsigned char>(0xFF & (totalIn >> 8));
    content += static_cast<unsigned char>(0xFF & (totalIn >> 16));
    content += static_cast<unsigned char>(0xFF & (totalIn >> 24));
}


}}}

#endif // #if defined (ACONNECT_USE_ZLIB)

